package hydrap2p.search;

import hydrap2p.ServiceLocation;
import hydrap2p.logging.Level;

import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

public class Listeners implements ListenerIface{
	private List<ListenerIface> listeners;
	private Map<Integer, List<ListenerIface>> searchlisteners;
	private static final String TAG="Search - Listeners";
	public Listeners()
	{
		listeners=new LinkedList<ListenerIface>();
		searchlisteners=new TreeMap<Integer, List<ListenerIface>>();
	}

	private static boolean subscribeListener(List<ListenerIface> list, ListenerIface listener)
	{
		SearchLogging.log(Level.VERBOSE, "Search - Subscribers", "Subscribing global search listener "+listener);
		synchronized(list)
		{
			if (!list.contains(listener))
				list.add(listener);
		}
		return true;
	}
	private static boolean unsubscribeListener(List<ListenerIface> list, ListenerIface listener)
	{
		SearchLogging.log(Level.VERBOSE, "Search - Subscribers", "Unsubscribing global search listener "+listener);
		synchronized(list)
		{
			return list.remove(listener);
		}
	}
	private static boolean unsubscribeListener(List<ListenerIface> list, ServiceLocation location)
	{
		SearchLogging.log(Level.VERBOSE, "Search - Subscribers", "Unsubscribing global search listener at "+location);
		boolean found=false;
		synchronized(list)
		{
			for (ListenerIface listener : list)
			{
				if (listener instanceof ListenerThrift)
				{
					ListenerThrift thrifter=(ListenerThrift)listener;
					if (thrifter.getLocation().equals(location))
					{
						list.remove(listener);
						found=true;
						break;
					}
				}
			}
		}
		return found;
	}
	public boolean subscribeListener(ListenerIface listener)
	{
		return subscribeListener(listeners,listener);
	}
	public boolean unsubscribeListener(ListenerIface listener)
	{
		return unsubscribeListener(listeners,listener);
	}
	public boolean unsubscribeListener(ServiceLocation location)
	{
		return unsubscribeListener(listeners,location);
	}
	public boolean subscribeSearchListener(ListenerIface listener, int searchid)
	{
		SearchLogging.log(Level.VERBOSE, "Search - Subscribers", "Subscribing listener "+listener+" to search id "+searchid);
		List<ListenerIface> temp;
		synchronized(searchlisteners)
		{
			List<ListenerIface> newlist;
			if (!searchlisteners.containsKey(searchid))
			{	
				temp=new LinkedList<ListenerIface>();
				searchlisteners.put(searchid, temp);
			}
			else
				temp=searchlisteners.get(searchid);
		}
		if (temp!=null)
			return subscribeListener(temp,listener);
		return false;
	}
	public boolean unsubscribeSearchListener(ListenerIface listener, int searchid)
	{
		SearchLogging.log(Level.VERBOSE, "Search - Subscribers", "Unsubscribing listener "+listener+" from search id "+searchid);
		List<ListenerIface> temp=getSearchListeners(searchid);
		if (temp!=null)
			return unsubscribeListener(temp,listener);
		return false;
	}
	public boolean unsubscribeSearchListener(ServiceLocation location, int searchid)
	{
		List<ListenerIface> temp=getSearchListeners(searchid);
		if (temp!=null)
			return unsubscribeListener(temp,location);
		return false;
	}
	
	private List<ListenerIface> getSearchListeners(int searchid)
	{
		List<ListenerIface> temp=null;
		synchronized(searchlisteners)
		{
			temp=searchlisteners.get(searchid);
			if (temp!=null)
				temp=new LinkedList<ListenerIface>(temp);
		}
		return temp;
	}

	@Override
	public void addSearchResult(int searchid, SearchResult result) {
		List<ListenerIface> mylisteners=getSearchListeners(searchid);
		List<ListenerIface> failedlisteners=new LinkedList<ListenerIface>();
		if (mylisteners==null)
			return;
		
		for (ListenerIface listener : mylisteners)
		{
			try {
				synchronized(listener)
				{
					listener.addSearchResult(searchid, result);
				}
			} catch (Exception e) {
				SearchLogging.log(Level.ERROR, TAG, "Failed to notify a listener about a new search result: "+e.getLocalizedMessage());
				failedlisteners.add(listener);
			}
		}
		for (ListenerIface listener:failedlisteners)
			unsubscribeSearchListener(listener, searchid);
		
	}

	@Override
	public void delSearch(int searchid) {
		List<ListenerIface> mylisteners;
		List<ListenerIface> failedlisteners=new LinkedList<ListenerIface>();
		synchronized(listeners)
		{
			mylisteners=new LinkedList<ListenerIface>(listeners);
		}
		for (ListenerIface listener : mylisteners)
		{
			try {
				listener.delSearch(searchid);
			} catch (Exception e) {
				SearchLogging.log(Level.ERROR, TAG, "Failed to notify a listener about a removed search: "+e.getLocalizedMessage());
				failedlisteners.add(listener);
			}
		}
		synchronized(listeners)
		{
			listeners.removeAll(failedlisteners);
		}
		synchronized(searchlisteners)
		{
			searchlisteners.remove(searchid);
		}
	}

	@Override
	public void newSearch(int searchid, SearchQuery query, List<String> providers) {
		List<ListenerIface> mylisteners;
		List<ListenerIface> failedlisteners=new LinkedList<ListenerIface>();
		synchronized(listeners)
		{
			mylisteners=new LinkedList<ListenerIface>(listeners);
		}
		for (ListenerIface listener : mylisteners)
		{
			try {
				listener.newSearch(searchid, query, providers);
			} catch (Exception e) {
				SearchLogging.log(Level.ERROR, TAG, "Failed to notify a listener about a new search: "+e.getLocalizedMessage());
				failedlisteners.add(listener);
			}
		}
		synchronized(listeners)
		{
			listeners.removeAll(failedlisteners);
		}
	}

	@Override
	public void searchFinished(int searchid) {
		List<ListenerIface> mylisteners;
		List<ListenerIface> failedlisteners=new LinkedList<ListenerIface>();
		synchronized(listeners)
		{
			mylisteners=new LinkedList<ListenerIface>(listeners);
		}
		for (ListenerIface listener : mylisteners)
		{
			try {
				listener.searchFinished(searchid);
			} catch (Exception e) {
				SearchLogging.log(Level.ERROR, TAG, "Failed to notify a listener about a finished search: "+e.getLocalizedMessage());
				failedlisteners.add(listener);
			}
		}
		synchronized(listeners)
		{
			listeners.removeAll(failedlisteners);
		}
	}

	@Override
	public void searchProviderFinished(int searchid, String name) {
		List<ListenerIface> mylisteners=getSearchListeners(searchid);
		List<ListenerIface> failedlisteners=new LinkedList<ListenerIface>();
		if (mylisteners==null)
			return;
		
		for (ListenerIface listener : mylisteners)
		{
			try {
				listener.searchProviderFinished(searchid, name);
			} catch (Exception e) {
				SearchLogging.log(Level.ERROR, TAG, "Failed to notify a listener about a finished search provider: "+e.getLocalizedMessage());
				failedlisteners.add(listener);
			}
		}
		
		for (ListenerIface listener:failedlisteners)
			unsubscribeSearchListener(listener, searchid);
	}

	@Override
	public void searchProviderStarted(int searchid, String name) {
		List<ListenerIface> mylisteners=getSearchListeners(searchid);
		List<ListenerIface> failedlisteners=new LinkedList<ListenerIface>();
		if (mylisteners==null)
			return;
		
		for (ListenerIface listener : mylisteners)
		{
			try {
				listener.searchProviderStarted(searchid, name);
			} catch (Exception e) {
				SearchLogging.log(Level.ERROR, TAG, "Failed to notify a listener about a started search provider: "+e.getLocalizedMessage());
				failedlisteners.add(listener);
			}
		}
		
		for (ListenerIface listener:failedlisteners)
			unsubscribeSearchListener(listener, searchid);
	}
}
